#!/usr/bin/python3
# encoding:utf-8

# API接口服务端-交易所
# 验证票据票号 DEMO
# 在 python 3.10.5 测试通过

# pip install bip44  
# pip install flask

from mnemonic import Mnemonic
from bip32 import BIP32, HARDENED_INDEX
from typing import Iterable, Tuple, Union
from coincurve import PrivateKey, PublicKey
from Crypto.Hash import keccak 

from Crypto import Random
from Crypto.Hash import SHA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
from Crypto.Signature import PKCS1_v1_5 as Signature_pkcs1_v1_5
from Crypto.PublicKey import RSA

import base64
import time
import json
import flask

# 实例化api，把当前这个python文件当作一个服务，__name__代表当前这个python文件
api = flask.Flask(__name__) 

# 加密解密：公钥加密，私钥解密
# 签名验签：私钥签名，公钥验签
# 无论是加密机密还是签名验签都使用同一对秘钥对

# 验证签名
def sign_verify(message ,signature) :
    # 公钥 client 对内容进行签名，然后再比较这个签名和base64解码后的签名是否一致，如果一致，返回 True
    with open('client-public.pem') as f:
        key = f.read()
        rsakey = RSA.importKey(key)
        verifier = Signature_pkcs1_v1_5.new(rsakey)
        digest = SHA.new()
        
        # 把STR字符串编码为 UTF-8
        message = message.encode('UTF-8')  
        digest.update(message)
        
        signature = base64.b64decode(signature)
        is_verify = verifier.verify(digest, signature)
        return is_verify

# 根据 appid 来验证商户是否存成，签名是否正确
def mch_verify(appid ,time_stamp ,signature) :
    if appid == '1160fc300abce69590556fbb0acec8ca' :
        message = str(appid) + str(time_stamp)
        signed = sign_verify(message ,signature)
        return sign_verify(message ,signature)
    else:
        return False

# keccak
def keccak_256(b: bytes) -> bytes:
    h = keccak.new(data=b, digest_bits=256)
    return h.digest()


# 检查ETH格式的地址
def to_checksum_addr(eth_addr: str) -> str:
    """
    Convert eth address to eth checksum address.

    EIP 55: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md#implementation
    """
    address = eth_addr.lower().replace("0x", "")
    addr_hash = keccak_256(address.encode()).hex()

    res = []
    for a, h in zip(address, addr_hash):
        if int(h, 16) >= 8:
            res.append(a.upper())
        else:
            res.append(a)

    return "0x" + "".join(res)


# 用公钥生成ETH格式的地址
def get_eth_addr(pk: Union[str, bytes]) -> str:
    """Get ETH address from a public key."""
    pk_bytes = bytes.fromhex(pk) if isinstance(pk, str) else pk

    if len(pk_bytes) != 64:
        pk_bytes = PublicKey(pk_bytes).format(False)[1:]

    return to_checksum_addr(f"0x{keccak_256(pk_bytes)[-20:].hex()}") 
    

# 用助记词生成种子，生成公私钥对    
def gen_bip32():
    # 助记词，12个英文单词  ,根据助记词可以得到种子 seed 
    mnemonic = "purity tunnel grid error scout long fruit false embody caught skin gate"
    
    # print(f"Mnemonic: {mnemonic}")
    
    # 根据助记词得到种子
    # language = "english"
    # passphrase = ""   
    seed = Mnemonic(language = "english").to_seed(mnemonic, passphrase = "")
    
    seed_hex = seed.hex() 
    # print(f"seed: {seed_hex}")      
    # "5844e009c4ab5088c2ae6f15e6d9097560bcacf95316f724d202ac849d1cc8c2da1b0f7b9b6cc00536ddc5b753f16bad4095ab35a6001d14ee37758ad29e1385" 
    
    # 用种子得到 bip32 对象
    bip32 = BIP32.from_seed(seed)    

    # root_key   # Chain m
    root_key = bip32.get_xpriv()    
    # print(f"Root Key: {root_key}")     
    # root_key = "xprv9s21ZrQH143K3Aukf4jbUU3EaLCekeHNDBUvFujxU5fACBbin8R6rzFZU8Q1JktbpeTEzCEAhhkvF9pPQwy6EFx3uxY19Yaf2TmXhRCtZj1"   
    
    # 派生路径为 m/44'/60'/1'/0
    # print("path: m/44'/60'/1'/0")    
    # xprv 扩展私钥  #交易所保管
    xprv =  bip32.get_xpriv_from_path("m/44'/60'/1'/0")     
    # print(f"xprv: {xprv}")        
    # xprv = "xprvA1wzUVhL969bgvQc2TKnoajUSPseKUda6DXGESEmA7QsA38GU8MGgbEERT2BQ9Jq2jZotsicGnzUHfhVNUhvRRo3nqxNb7dxjpABmozpyT5"  
    
    xpub =  bip32.get_xpub_from_path("m/44'/60'/1'/0")  
    # xpub = "xpub6EwLt1EDyThtuQV58UroAigCzRi8iwMRTSSs2peNiSwr2qTR1ffXEPYiGhMJ3pwSx8RKfaABjGhsu9raX27shVVzeDkZSRiz3JFhPCm6Czf"    
    return xprv ,xpub

# 对传入的票据集，每张进行检查    
def token_verify(token_dict,xprv): 
    bip32 = BIP32.from_xpriv(xprv)
 
    new_dict = {}
    i = 0
    while i < 9:
        i += 1
        # print (i)
        seq_dict = {}
        
        str_i = str(i)
        this_dict = token_dict[str_i]
        # print (this_dict)      
        
        path = "m/44'/60'/1'/0/"+ str( this_dict["sequence"] )
        token = this_dict["token"] 
        sign = this_dict["sign"]            

        addr_path = "m/"+ str(i)
        pk =  bip32.get_pubkey_from_path(addr_path) 
        addr =  get_eth_addr(pk)
        
        if token == addr: 
          if sign_verify(addr ,sign):
            seq_dict["path"] = path
            seq_dict["token"] = addr      
            new_dict[str_i] = seq_dict
    return new_dict
    
 
# -----------------------------------------  # -------------- # 接口定义 # ----- 
#post入参访问方式三：josn格式参数  
@api.route('/users',methods=['post'])
def users():
  #from-data格式参数
  timestamp = flask.request.json.get('timestamp')
  appid = flask.request.json.get('appid')
  signature = flask.request.json.get('signature')  
  data = flask.request.json.get('data')
  
  # 参数不能为空
  if appid and signature and timestamp and data: 
    msg_time_stamp = int(timestamp)
    expires_time = int(time.time()-3600)
    
    # 接收到时间戳在1小时前的判断为过期，不做后续处理
    if  msg_time_stamp < expires_time:
        ren = {'code':1002 ,'msg':'消息已过期' }      
    elif mch_verify(appid ,timestamp ,signature) :
        # ren = {'msg':'签名验证成功,数据已接收','msg_code':200 }    
        data = {"results":[{"mobile":"17683853671","exist":True},{"mobile":"17683853672","exist":False}]}
        ren = {'code':200 ,'msg':'消息获取成功',"data" :data  }
    else:
        ren = {'code':-1 ,'msg':'商户号或签名错误' }
  else:
    ren = {'msg':'缺少必要的参数','code':1001 }
  return json.dumps(ren,ensure_ascii=False)

#post入参访问方式三：josn格式参数  
@api.route('/issue',methods=['post'])
def issue():
  #from-data格式参数
  timestamp = flask.request.json.get('timestamp')
  appid = flask.request.json.get('appid')
  signature = flask.request.json.get('signature')  
  data = flask.request.json.get('data')
  
  # 参数不能为空
  if appid and signature and timestamp and data: 
    msg_time_stamp = int(timestamp)
    expires_time = int(time.time()-3600)
    
    # 接收到时间戳在1小时前的判断为过期，不做后续处理
    if  msg_time_stamp < expires_time:
        ren = {'code':1002 ,'msg':'消息已过期' }      
    elif mch_verify(appid ,timestamp ,signature) :
        # ren = {'msg':'签名验证成功,数据已接收','msg_code':200 }     
        # 生产环境中的 本接口，接收到数据，缓存到本地就直接返回成功的提示信息，不对数据做进一步的计算，否则会造成连接超时。
        # 生产环境中用 异步方式来处理这些数据

        # 在本 DEMO 中继续对数据 data 里的每一张票进行验证
        # xprv ,xpub = gen_bip32()
        xprv = "xprvA1wzUVhL969bgvQc2TKnoajUSPseKUda6DXGESEmA7QsA38GU8MGgbEERT2BQ9Jq2jZotsicGnzUHfhVNUhvRRo3nqxNb7dxjpABmozpyT5" 
        new_dict = token_verify(data,xprv)
        ren = {'code':200 ,'msg':'验证签名成功,数据已接收,已核对票据集',"data" :new_dict  }
    else:
        ren = {'code':-1 ,'msg':'商户号或签名错误' }
  else:
    ren = {'msg':'缺少必要的参数','code':1001 }
  return json.dumps(ren,ensure_ascii=False)
  

# ########## ------------------- ############## -- 交易所 -----------  
# 服务端主程序入口  
if __name__ == '__main__':
  api.run(port=8888,debug=True,host='127.0.0.1') # 启动服务
  # debug=True,改了代码后，不用重启，它会自动重启
  # 'host='127.0.0.1' 别IP访问地址